昨天先講了使用方式
接著來講一下底層的部分,以及 _rpc
是怎麼出現的
會額外提是因為筆者剛接觸時
會看到有些模組是直接 require('web.ajax')
下意識會覺得跟 _rpc
一樣,但又說不出有為什麼相同
首先,先全域搜尋關鍵字 _rpc: function
,可以找到定義位置
// addons/web/static/src/js/core/service_mixins.js
_rpc: function (params, options) {
var query = rpc.buildQuery(params);
var prom = this.call('ajax', 'rpc', query.route, query.params, options, this);
if (!prom) {
prom = new Promise(function () {});
prom.abort = function () {};
}
var abort = prom.abort ? prom.abort : prom.reject;
if (!abort) {
throw new Error("a rpc promise should always have a reject function");
}
prom.abort = abort.bind(prom);
return prom;
},
可以發現主要的兩行,其一是 rpc
,其二是 call()
var query = rpc.buildQuery(params);
var prom = this.call('ajax', 'rpc', query.route, query.params, options, this);
第一個 rpc
就很簡單了,跳到宣告點,就能發現就是 require
的結果
// addons/web/static/src/js/core/service_mixins.js
odoo.define('web.ServicesMixin', function (require) {
"use strict";
var rpc = require('web.rpc');
// ...
而 web.rpc
主要的功能就是組合 method
, model
… 等等內容,符合 odoo 本身底層的統一資料包
為什麼會這麼說呢?
看一下 buildQuery()
內容就知道了
// addons/web/static/src/js/core/rpc.js
buildQuery: function (options) {
var route;
var params = options.params || {};
var orderBy;
if (options.route) {
route = options.route;
} else if (options.model && options.method) {
route = '/web/dataset/call_kw/' + options.model + '/' + options.method;
}
if (options.method) {
params.args = options.args || [];
params.model = options.model;
params.method = options.method;
params.kwargs = _.extend(params.kwargs || {}, options.kwargs);
params.kwargs.context = options.context || params.context || params.kwargs.context;
}
if (options.method === 'read_group' || options.method === 'web_read_group') {
if (!(params.args && params.args[0] !== undefined)) {
params.kwargs.domain = options.domain || params.domain || params.kwargs.domain || [];
}
if (!(params.args && params.args[1] !== undefined)) {
params.kwargs.fields = options.fields || params.fields || params.kwargs.fields || [];
}
if (!(params.args && params.args[2] !== undefined)) {
params.kwargs.groupby = options.groupBy || params.groupBy || params.kwargs.groupby || [];
}
params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
// In kwargs, we look for "orderby" rather than "orderBy" (note the absence of capital B),
// since the Python argument to the actual function is "orderby".
orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
params.kwargs.orderby = orderBy ? rpc._serializeSort(orderBy) : orderBy;
params.kwargs.lazy = 'lazy' in options ? options.lazy : params.lazy;
if (options.method === 'web_read_group') {
params.kwargs.expand = options.expand || params.expand || params.kwargs.expand;
params.kwargs.expand_limit = options.expand_limit || params.expand_limit || params.kwargs.expand_limit;
var expandOrderBy = options.expand_orderby || params.expand_orderby || params.kwargs.expand_orderby;
params.kwargs.expand_orderby = expandOrderBy ? rpc._serializeSort(expandOrderBy) : expandOrderBy;
}
}
if (options.method === 'search_read') {
// call the model method
params.kwargs.domain = options.domain || params.domain || params.kwargs.domain;
params.kwargs.fields = options.fields || params.fields || params.kwargs.fields;
params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
// In kwargs, we look for "order" rather than "orderBy" since the Python
// argument to the actual function is "order".
orderBy = options.orderBy || params.orderBy || params.kwargs.order;
params.kwargs.order = orderBy ? rpc._serializeSort(orderBy) : orderBy;
}
if (options.route === '/web/dataset/search_read') {
// specifically call the controller
params.model = options.model || params.model;
params.domain = options.domain || params.domain;
params.fields = options.fields || params.fields;
params.limit = options.limit || params.limit;
params.offset = options.offset || params.offset;
orderBy = options.orderBy || params.orderBy;
params.sort = orderBy ? rpc._serializeSort(orderBy) : orderBy;
params.context = options.context || params.context || {};
}
return {
route: route,
params: JSON.parse(JSON.stringify(params)),
};
},
有沒有發現前面的判斷,有 model
跟 method
時, route
會有額外組合
// addons/web/static/src/js/core/rpc.js
// ...
if (options.route) {
route = options.route;
} else if (options.model && options.method) {
route = '/web/dataset/call_kw/' + options.model + '/' + options.method;
}
// ...
而有些模組會是直接 const rpc = require('web.rpc')
這樣也是可以,但要注意的是呼叫的函式不是 rpc()
,是 query()
所以 _rpc()
跟 rpc.query()
做的內容其實是一樣的
只是前者做了額外的錯誤處理
接著第二個 call()
this.call('ajax', 'rpc', query.route, query.params, options, this);
看一下 call()
的函式,會發現是 trigger_up
服務 ajax
// addons/web/static/src/js/core/service_mixins.js
call: function (service, method) {
var args = Array.prototype.slice.call(arguments, 2);
var result;
this.trigger_up('call_service', {
service: service,
method: method,
args: args,
callback: function (r) {
result = r;
},
});
return result;
},
因為中間跳了幾層,所以省略了,有興趣的朋友可以自行翻一下
總之可以看在 web
模組的 service 的註冊
// addons/web/static/src/js/services/ajax_service.js
odoo.define('web.AjaxService', function (require) {
"use strict";
var AbstractService = require('web.AbstractService');
var ajax = require('web.ajax');
var core = require('web.core');
var session = require('web.session');
var AjaxService = AbstractService.extend({
/**
* @param {Object} libs - @see ajax.loadLibs
* @param {Object} [context] - @see ajax.loadLibs
* @param {Object} [tplRoute] - @see ajax.loadLibs
*/
loadLibs: function (libs, context, tplRoute) {
return ajax.loadLibs(libs, context, tplRoute);
},
rpc: function (route, args, options, target) {
var rpcPromise;
var promise = new Promise(function (resolve, reject) {
rpcPromise = session.rpc(route, args, options);
rpcPromise.then(function (result) {
if (!target.isDestroyed()) {
resolve(result);
}
}).guardedCatch(function (reason) {
if (!target.isDestroyed()) {
reject(reason);
}
});
});
promise.abort = rpcPromise.abort.bind(rpcPromise);
return promise;
},
});
core.serviceRegistry.add('ajax', AjaxService);
return AjaxService;
});
從中不難發現就是執行了這個服務的 rpc()
但各位有沒有注意到,這幾個檔案追蹤到最後都有 require('web.ajax')
odoo 底層已經有包裝好往後端溝通的函式了,就不用重複造輪子
各位有興趣可以自行看一下 addons/web/static/src/js/core/ajax.js
最後為什麼要有 web.ajax
跟 web.rpc
的差別
簡單來說就是開發時寫的內容多寡
如果是使用 web.ajax
, url
的部分就要寫的完整
但 web.rpc
已經有包裝相關內容了,就不用這麼麻煩
各位可以思考一下下面的程式
例子比較簡單,但內容若是比較多的時候,而且要重複寫很多次
那麼哪一個會比較能夠容易懂
const ajax = require('web.ajax');
const rpc = require('web.rpc');
ajax.rpc(
"/web/dataset/call_kw/res.users/read",
{
args: [[1001, 1002, 1003]],
}
)
rpc.query({
model: 'res.partner',
method: 'read',
args: [[1001, 1002, 1003]]
})